﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Xml.Linq;
using System.Linq;
using System.Reflection;
using System.IO;
using System.Xml.Schema;
using Microsoft.SharePoint.Administration;
using Codeplex.SPCleanup.Library.Utils; 

namespace Codeplex.SPCleanup.SPUtilities.Cleanup
{
    public static class ConfigManager
    {              
        private static XNamespace ns = CleanupConstants.CLEANUPSCHEMA_XSD_NAMESPACE;                
        private const string CleanupConfigSettingsPropKey = "CleanupConfig";        
        private const string ConfigSchemaResource = "Codeplex.SPCleanup.SPUtilities.CleanupJobSchema.xsd";

        /// <summary>
        /// This methods returns the cleanup settings for a list in current spcontext
        /// </summary>
        /// <returns></returns>
        public static SiteDefinition GetCleanupSettingsForList()
        {
            return GetCleanupSettingsForList(SPContext.Current.ListId.ToString()); 
        }

        /// <summary>
        /// This methods returns the cleanup settings for a list
        /// </summary>
        /// <param name="listId">Guid of the list</param>
        /// <returns>CleanupList object or null if list does not exist</returns>
        public static SiteDefinition GetCleanupSettingsForList(string listId)
        {
            // validate the input parameters
            if (0 == listId.Trim().Length)
            {
                throw new ArgumentException("List Id parameter cannot be null or empty", "listId");
            }                                   

            return GetListCleanUpInfo(listId);
        }        

        /// <summary>
        /// This methods returns the cleanup settings in all the site definitions where cleanup is enabled
        /// </summary>        
        /// <returns>Collection of site definition objects</returns>
        public static IEnumerable<SiteDefinition> GetCleanupSettings(SPSite site)
        {
            IEnumerable<SiteDefinition> cleanupSettings = null;          

            string cleanUpXml = GetListCleanupJobXml(site);

            if (null != cleanUpXml && cleanUpXml.Length > 0)
            {
                XDocument cleanUpInfo = XDocument.Parse(cleanUpXml);

                bool isValid = ValidateConfigSchema(cleanUpInfo);

                if (isValid)
                {
                  cleanupSettings = from siteDefInfo in cleanUpInfo.Descendants(ns + CleanupConstants.SITEDEFINITION_ELEMENTNAME)
                                                                  where CheckBooleanAttribute(siteDefInfo.Attribute(CleanupConstants.ENABLED_ATTRIBUTENAME)) == true
                                                                  select new SiteDefinition 
                                                                  {
                                                                      Enabled = CheckBooleanAttribute(siteDefInfo.Attribute(CleanupConstants.ENABLED_ATTRIBUTENAME)),
                                                                      SiteUri = new Uri(siteDefInfo.Attribute(CleanupConstants.URI_ATTRIBUTENAME).Value),                                                                      
                                                                      ID = new Guid(siteDefInfo.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value),
                                                                      Sites = 
                                                                      (
                                                                          from siteInfo in siteDefInfo.Elements(ns + CleanupConstants.SITE_ELEMENTNAME)
                                                                          select new Site
                                                                          {
                                                                              Enabled = CheckBooleanAttribute(siteInfo.Attribute(CleanupConstants.ENABLED_ATTRIBUTENAME)),
                                                                              ID = new Guid(siteInfo.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value),
                                                                              Lists = 
                                                                              (
                                                                                  from listInfo in siteInfo.Elements(ns + CleanupConstants.LIST_ELEMENTNAME)
                                                                                  select new SiteList
                                                                                  {
                                                                                      Enabled = CheckBooleanAttribute(listInfo.Attribute(CleanupConstants.ENABLED_ATTRIBUTENAME)),
                                                                                      ID=new Guid(listInfo.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value),
                                                                                      CleanupInfo = new ListCleanupInfo
                                                                                      {
                                                                                          ItemTimeSpan = CheckTimeSpanAttribute(listInfo.Element(ns + CleanupConstants.CLEANUPINFO_ELEMENTNAME).Attribute(CleanupConstants.CLEANUPINFO_TIMESPAN_ATTRIBUTENAME)),
                                                                                          BrowseSubfolders = CheckBooleanAttribute(listInfo.Element(ns + CleanupConstants.CLEANUPINFO_ELEMENTNAME).Attribute(CleanupConstants.CLEANUPINFO_BROWSESUBFOLDERS_ATTRIBUTENAME)),
                                                                                          DeleteSubfolders = CheckBooleanAttribute(listInfo.Element(ns + CleanupConstants.CLEANUPINFO_ELEMENTNAME).Attribute(CleanupConstants.CLEANUPINFO_DELETESUBFOLDERS_ATTRIBUTENAME)),
                                                                                          EmailOwner = CheckBooleanAttribute(listInfo.Element(ns + CleanupConstants.CLEANUPINFO_ELEMENTNAME).Attribute(CleanupConstants.CLEANUPINFO_EMAIL_ATTRIBUTENAME)),
                                                                                      }
                                                                                  }
                                                                              ).ToList()
                                                                          }
                                                                      ).ToList()
                                                                  };
                }
            }
            
            return cleanupSettings;
        }

        /// <summary>
        /// Updates the config with the cleanup settings
        /// </summary>
        /// <param name="cleanupSettings">Cleanup settings information</param>
        /// <returns>true or false</returns>
        public static void UpdateListCleanupSettings(SiteDefinition cleanupSettings) 
        {
            // validate the argument
            if (null == cleanupSettings)
            {
                throw new ArgumentException("Cleanup settings argument cannot be null");
            }
            
            string cleanUpXml = GetListCleanupJobXml();

            if (0 == cleanUpXml.Trim().Length)
            {
                // if the list is empty put the default xml packet.
                cleanUpXml = string.Format(@"<CleanupJob xmlns=""{0}""></CleanupJob>",CleanupConstants.CLEANUPSCHEMA_XSD_NAMESPACE);
            }

            if (null != cleanUpXml && cleanUpXml.Length > 0)
            {
                XDocument cleanUpInfo = XDocument.Parse(cleanUpXml);


                bool isValid = ValidateConfigSchema(cleanUpInfo);

                if (isValid)
                {
                    if (cleanupSettings.Validate())
                    {
                        XElement siteDefNode = CheckAndAddSiteDefinitionNode(cleanUpInfo, cleanupSettings);
                        XElement siteNode, listNode, cleanUpInfoNode;

                        foreach (Site siteInfo in cleanupSettings.Sites)
                        {
                            if (siteInfo.Validate())
                            {
                                siteNode = CheckAndAddSiteNode(siteDefNode, siteInfo);
                                foreach (SiteList listInfo in siteInfo.Lists)
                                {
                                    if (listInfo.Validate())
                                    {
                                        listNode = CheckAndAddListNode(siteNode, listInfo);
                                        if (null != listInfo.CleanupInfo)
                                        {
                                            cleanUpInfoNode = CheckAndAddCleanUpNode(listNode, listInfo.CleanupInfo);
                                        }
                                    }
                                    else
                                    {
                                        throw new ArgumentException("List attributes are null or empty");
                                    }
                                }
                            }
                            else
                            {
                                throw new ArgumentException("Site attributes are null or empty");
                            }
                        }
                        UpdateCleanUpJobXml(cleanUpInfo.ToString());
                    }
                    else
                    {
                        throw new ArgumentException("Site definition attributes are null or empty");
                    }                    
                }
            }            
        }
        
        /// <summary>
        /// Returns the cleanup list object containing the cleanup settings for a list
        /// </summary>
        /// <param name="listId">List GUID</param>
        /// <returns>cleanup list object or null</returns>
        private static SiteDefinition GetListCleanUpInfo(string listId)
        {
            SiteDefinition listCleanupInfo = null;            

            string cleanUpXml = GetListCleanupJobXml();

            if (null != cleanUpXml && cleanUpXml.Length > 0)
            {
                XDocument cleanUpInfo = XDocument.Parse(cleanUpXml);

                bool isValid = ValidateConfigSchema(cleanUpInfo);

                if (isValid)
                {                    
                    IEnumerable<SiteDefinition> listCleanupInfoCol = from listInfo in cleanUpInfo.Descendants(ns + CleanupConstants.LIST_ELEMENTNAME)
                                                                  where listInfo.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value.ToUpper() == listId.ToUpper()
                                                                  let siteDefInfo = listInfo.Ancestors(ns + CleanupConstants.SITEDEFINITION_ELEMENTNAME).First()
                                                                  select new SiteDefinition
                                                                  {
                                                                      ID = new Guid(siteDefInfo.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value),
                                                                      SiteUri = new Uri(siteDefInfo.Attribute(CleanupConstants.URI_ATTRIBUTENAME).Value),
                                                                      Enabled = CheckBooleanAttribute(siteDefInfo.Attribute(CleanupConstants.ENABLED_ATTRIBUTENAME)),
                                                                      Sites =
                                                                      (
                                                                        from siteInfo in listInfo.Ancestors(ns + CleanupConstants.SITE_ELEMENTNAME)
                                                                        select new Site
                                                                        {
                                                                            ID = new Guid(siteInfo.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value),
                                                                            Enabled = CheckBooleanAttribute(siteInfo.Attribute(CleanupConstants.ENABLED_ATTRIBUTENAME)),
                                                                            Lists = 
                                                                            (
                                                                                from cleanupInfo in listInfo.Elements(ns + CleanupConstants.CLEANUPINFO_ELEMENTNAME)
                                                                                select new SiteList 
                                                                                {
                                                                                    ID=new Guid(cleanupInfo.Parent.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value),
                                                                                    Enabled=CheckBooleanAttribute(cleanupInfo.Parent.Attribute(CleanupConstants.ENABLED_ATTRIBUTENAME)),
                                                                                    CleanupInfo= new ListCleanupInfo
                                                                                    {
                                                                                      ItemTimeSpan = CheckTimeSpanAttribute(cleanupInfo.Attribute(CleanupConstants.CLEANUPINFO_TIMESPAN_ATTRIBUTENAME)),
                                                                                      BrowseSubfolders = CheckBooleanAttribute(cleanupInfo.Attribute(CleanupConstants.CLEANUPINFO_BROWSESUBFOLDERS_ATTRIBUTENAME)),
                                                                                      DeleteSubfolders = CheckBooleanAttribute(cleanupInfo.Attribute(CleanupConstants.CLEANUPINFO_DELETESUBFOLDERS_ATTRIBUTENAME)),
                                                                                      EmailOwner = CheckBooleanAttribute(cleanupInfo.Attribute(CleanupConstants.CLEANUPINFO_EMAIL_ATTRIBUTENAME))
                                                                                    }
                                                                                }
                                                                            ).ToList()
                                                                        }
                                                                      ).ToList()                                                                    
                                                                  };


                    //listid is unique there is only one element
                    if (null != listCleanupInfoCol && listCleanupInfoCol.Count() > 0)
                    {
                        listCleanupInfo = listCleanupInfoCol.First();
                    }
                }
            }

            return listCleanupInfo;
        }

        /// <summary>
        /// Uses the current context to get the web application reference to retrieve the cleanup xml  
        /// </summary>
        /// <returns></returns>
        private static string GetListCleanupJobXml()
        {
            string cleanupJobXml = string.Empty;
            Log.Debug("Function GetListCleanupJobXml (no params) called");
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                Log.Debug("GetListCleanupJobXml (no params): Creating the site collection reference using elevated privileges for site : " + SPContext.Current.Site.ID.ToString());
                using (SPSite elevatedSiteCol = new SPSite(SPContext.Current.Site.ID))
                {
                    cleanupJobXml = GetListCleanupJobXml(elevatedSiteCol);
                }
            });

            return cleanupJobXml;
        }

        /// <summary>
        /// Opens the site collection to read the cleanup job info xml
        /// </summary>
        /// <returns>cleanupjob info xml string</returns>
        private static string GetListCleanupJobXml(SPSite site)
        {
            string cleanUpXml = string.Empty;

            if (null != site)
            {
                Log.Debug("GetListCleanupJobXml (with params) : Retrieving the cleanup config information for sitecollection : " + site.Url);                
                using (SPWeb rootWeb = site.RootWeb)
                {
                    if (rootWeb.Properties.ContainsKey(CleanupConfigSettingsPropKey))
                    {
                        cleanUpXml = rootWeb.Properties[CleanupConfigSettingsPropKey];
                        Log.Debug("GetListCleanupJobXml (with params) : Cleanupjob : " + cleanUpXml);
                    }
                }
            }            
            return cleanUpXml;
        }        

        /// <summary>
        /// Checks the boolean attribute node and returns true or false
        /// </summary>
        /// <param name="xAttr">Attribute node</param>
        /// <returns>true or false</returns>
        private static bool CheckBooleanAttribute(XAttribute xAttr)
        {
            bool retValue = false;

            if (null != xAttr)
            {
                bool.TryParse(xAttr.Value, out retValue); 
            }

            return retValue;
        }

        /// <summary>
        /// Checks the boolean attribute node and returns true or false
        /// </summary>
        /// <param name="xAttr">Attribute node</param>
        /// <returns>true or false</returns>
        private static float CheckTimeSpanAttribute(XAttribute xAttr)
        {
            float retValue = -1;

            if (null != xAttr)
            {
                float.TryParse(xAttr.Value, out retValue);
            }

            return retValue;
        }

        /// <summary>
        /// Validates the xml using the xsd
        /// </summary>
        /// <param name="cleanupConfigDoc"></param>
        /// <returns></returns>
        private static bool ValidateConfigSchema(XDocument cleanupConfigDoc)
        {
            StreamReader configSchema = null;
            bool isValid = false;

            try
            {
                configSchema = GetConfigSchema();

                XmlSchemaSet configSchemaset = new XmlSchemaSet();

                configSchemaset.Add(XmlSchema.Read(configSchema, null));

                cleanupConfigDoc.Validate(configSchemaset, null);

                isValid = true;
            }

            catch (Exception ex)
            {
                //log the schema validation error
                Log.Error("Error in validating the config xml schema: " + ex.Message);
                throw;
            }

            finally
            {
                if (null != configSchema)
                {
                    configSchema.Close();
                }
            }

            return isValid;
        }        

        /// <summary>
        /// Reads the schema file from the assembly resource
        /// </summary>
        /// <returns></returns>
        private static StreamReader GetConfigSchema()
        {
            StreamReader resourceReader = null;
            Assembly asm = Assembly.GetExecutingAssembly();
            string[] resourceNames = asm.GetManifestResourceNames();
            foreach (string resourceName in resourceNames)
            {
                if (string.Compare(resourceName, ConfigSchemaResource, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    resourceReader = new StreamReader(asm.GetManifestResourceStream(resourceName));
                }
            }

            return resourceReader; 
        }

        /// <summary>
        /// Helper functions to form the cleanup job info xml. Check whether sitedef node exists for the current list and add it if it is not else
        /// returns the existing sitedef node
        /// </summary>
        /// <param name="cleanUpInfoNode">Cleanup job root node</param>
        /// <param name="siteDefUrl">site definition url</param>
        /// <param name="siteId">site id</param>
        /// <returns>Sitedefinition node</returns>
        private static XElement CheckAndAddSiteDefinitionNode(XDocument cleanUpInfoNode, SiteDefinition siteDefinitionInfo)
        {
            XElement siteDefitionNode = null;

            if (null != cleanUpInfoNode)
            {

                IEnumerable<XElement> siteDefElements = from siteDefInfo in cleanUpInfoNode.Descendants(ns + CleanupConstants.SITEDEFINITION_ELEMENTNAME)
                                                        where siteDefInfo.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value.ToUpper() == siteDefinitionInfo.ID.ToString().ToUpper()
                                                        select siteDefInfo;

                if (null != siteDefElements && 0 < siteDefElements.Count())
                {
                    siteDefitionNode = siteDefElements.First();
                }
                else
                {
                    // create the node and append to cleanUpInfoNode
                    siteDefitionNode = new XElement(ns + CleanupConstants.SITEDEFINITION_ELEMENTNAME);

                    cleanUpInfoNode.Root.Add(siteDefitionNode);
                }

                //if there is no sitedef node create it and set the enabled to true
                siteDefitionNode.SetAttributeValue(CleanupConstants.ENABLED_ATTRIBUTENAME, siteDefinitionInfo.Enabled);

                //set the id attribute
                siteDefitionNode.SetAttributeValue(CleanupConstants.ID_ATTRIBUTENAME, siteDefinitionInfo.ID);

                // set the url attribute
                siteDefitionNode.SetAttributeValue(CleanupConstants.URI_ATTRIBUTENAME, siteDefinitionInfo.SiteUri);

                
                
            }
            return siteDefitionNode;

        }

        /// <summary>
        /// Helper functions to form the cleanup job info xml. Check whether site node exists for the current list under the sitedef not 
        /// and add it if it is not else returns the existing site node
        /// </summary>
        /// <param name="siteDefNode">Sitedef root node</param>        
        /// <param name="webId">site id</param>
        /// <returns>Site node</returns>
        private static XElement CheckAndAddSiteNode(XElement siteDefNode, Site siteInfo)
        {
            XElement siteNode = null;

            if (null != siteDefNode)
            {

                IEnumerable<XElement> siteElements = from siteInfoNode in siteDefNode.Descendants(ns + CleanupConstants.SITE_ELEMENTNAME)
                                                     where siteInfoNode.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value.ToUpper() == siteInfo.ID.ToString().ToUpper()
                                                     select siteInfoNode;

                if (null != siteElements && 0 < siteElements.Count())
                {
                    siteNode = siteElements.First();
                }
                else
                {
                    // create the node and append to cleanUpInfoNode
                    siteNode = new XElement(ns + CleanupConstants.SITE_ELEMENTNAME);
                    siteDefNode.Add(siteNode);          
                }
                
                //if there is no site node create it and set the enabled to true
                siteNode.SetAttributeValue(CleanupConstants.ENABLED_ATTRIBUTENAME, siteInfo.Enabled);

                // set the id attribute
                siteNode.SetAttributeValue(CleanupConstants.ID_ATTRIBUTENAME, siteInfo.ID);                      
            }

            return siteNode;
        }

        /// <summary>
        /// Helper functions to form the cleanup job info xml. Check whether list node exists for the current list under the site  
        /// and add it if it is not else returns the existing list node
        /// </summary>
        /// <param name="siteNode">Site node</param>
        /// <param name="listId">list id</param>
        /// <returns>List node</returns>
        private static XElement CheckAndAddListNode(XElement siteNode, SiteList listInfo)
        {
            XElement listNode = null;

            if (null != siteNode)
            {

                IEnumerable<XElement> listElements = from listInfoNode in siteNode.Descendants(ns + CleanupConstants.LIST_ELEMENTNAME)
                                                     where listInfoNode.Attribute(CleanupConstants.ID_ATTRIBUTENAME).Value.ToUpper() == listInfo.ID.ToString().ToUpper()
                                                     select listInfoNode;

                if (null != listElements && 0 < listElements.Count())
                {
                    listNode = listElements.First();                    
                }
                else
                {
                    // create the node and append to cleanUpInfoNode
                    listNode = new XElement(ns + CleanupConstants.LIST_ELEMENTNAME);
                    siteNode.Add(listNode);   
                }

                //if there is no list node create it and set the enabled to true
                listNode.SetAttributeValue(CleanupConstants.ENABLED_ATTRIBUTENAME, listInfo.Enabled);

                // set the id attribute
                listNode.SetAttributeValue(CleanupConstants.ID_ATTRIBUTENAME, listInfo.ID);
                             
            }

            return listNode;
        }

        /// <summary>
        /// Helper functions to form the cleanup job info xml. Check whether cleanup info node exists for the current list under the list  
        /// and add it if it is not else updates the existing cleanup node and returns it
        /// </summary>
        /// <param name="listNode">List node</param>
        /// <returns>cleanup info node</returns>
        private static XElement CheckAndAddCleanUpNode(XElement listNode, ListCleanupInfo cleanupInfo)
        {
            XElement cleanUpInfoNode = null;

            if (null != listNode)
            {
                cleanUpInfoNode = listNode.Element(ns + CleanupConstants.CLEANUPINFO_ELEMENTNAME);

                if (null == cleanUpInfoNode)
                {
                    cleanUpInfoNode = new XElement(ns + CleanupConstants.CLEANUPINFO_ELEMENTNAME);

                    //add the cleanupinfo node
                    listNode.Add(cleanUpInfoNode);
                }

                // update the cleanup attributes                
                cleanUpInfoNode.SetAttributeValue(CleanupConstants.CLEANUPINFO_TIMESPAN_ATTRIBUTENAME, cleanupInfo.ItemTimeSpan);
                cleanUpInfoNode.SetAttributeValue(CleanupConstants.CLEANUPINFO_BROWSESUBFOLDERS_ATTRIBUTENAME, cleanupInfo.BrowseSubfolders);
                cleanUpInfoNode.SetAttributeValue(CleanupConstants.CLEANUPINFO_DELETESUBFOLDERS_ATTRIBUTENAME, cleanupInfo.DeleteSubfolders);
                cleanUpInfoNode.SetAttributeValue(CleanupConstants.CLEANUPINFO_EMAIL_ATTRIBUTENAME, cleanupInfo.EmailOwner);
            }

            return cleanUpInfoNode;
        }

        /// <summary>
        /// Updates the central admin config list with the updated cleanup job xml for this list
        /// </summary>
        /// <param name="cleanUpJobXml">cleanup job xml</param>
        /// <returns></returns>
        private static void UpdateCleanUpJobXml(string cleanUpJobXml)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                Log.Debug("Function UpdateCleanUpJobXml : Creating the site reference for ID : " + SPContext.Current.Site.ID.ToString());
                using (SPSite elevatedSiteCol = new SPSite(SPContext.Current.Site.ID))
                {
                    Log.Debug("Function UpdateCleanUpJobXml : Retrieving the root web reference for site");
                    using (SPWeb elevatedRootWeb = elevatedSiteCol.RootWeb)
                    {
                        if (elevatedRootWeb.Properties.ContainsKey(CleanupConfigSettingsPropKey))
                        {
                            Log.Debug("Function UpdateCleanUpJobXml : Creating the cleanupxml property key");
                            elevatedRootWeb.Properties[CleanupConfigSettingsPropKey] = cleanUpJobXml;                            
                        }
                        else
                        {
                            Log.Debug("Function UpdateCleanUpJobXml : updating the xml in properties bag : " + cleanUpJobXml);
                            elevatedRootWeb.Properties.Add(CleanupConfigSettingsPropKey, cleanUpJobXml);
                        }
                        Log.Debug("Function UpdateCleanUpJobXml : Calling allowunsafeupdates");
                        elevatedRootWeb.AllowUnsafeUpdates = true;
                        Log.Debug("Function UpdateCleanUpJobXml : Calling updates on the property bag");
                        elevatedRootWeb.Properties.Update();
                    }                    
                }                
            });

        }
    }
}
